﻿using System;
using System.Threading;
using System.Reflection;

namespace PortableTPL
{
    public partial class Task : IDisposable
    {
        static readonly TaskFactory factory = new TaskFactory();

        public static TaskFactory Factory
        {
            get { return factory; }
        }

        ManualResetEvent waitEvent = new ManualResetEvent(false);

        // State
        TaskStatus status = TaskStatus.Created;

        // Exception
        AggregateException aggregateException;

        // Properties
        public bool IsCompleted
        {
            get { return Status == TaskStatus.Faulted || Status == TaskStatus.RanToCompletion || Status == TaskStatus.Canceled; }
        }

        public bool IsFaulted
        {
            get { return Status == TaskStatus.Faulted; }
        }

        public bool IsCanceled
        {
            get { return Status == TaskStatus.Canceled; }
        }

        public TaskStatus Status
        {
            get { return status; }
        }

        public object AsyncState { get; protected set; }

        public AggregateException Exception
        {
            get { return aggregateException; }
        }

        protected Delegate ActionDelegate { get; set; }

        public ManualResetEvent WaitEvent
        {
            get { return waitEvent; }
        }

        // Methods
        protected Task()
        {

        }

        public Task(Action action)
        {
            ActionDelegate = (Delegate)action;
        }

        public Task(Action<object> action, object state)
        {
            ActionDelegate = action;
            AsyncState = state;
        }

        public Task(Action action, CancellationToken token)
        {
            ActionDelegate = (Delegate)action;
            token.Register(OnCancellation);
        }

        public Task(Action<object> action, object state, CancellationToken token)
        {
            ActionDelegate = action;
            AsyncState = state;

            token.Register(OnCancellation);
        }

        protected void OnCancellation()
        {
            status = TaskStatus.Canceled;
        }

        internal void SetIsScheduled()
        {
            status = TaskStatus.WaitingToRun;
        }

        public void Start()
        {
            Start(TaskFactory.StandardScheduler);
        }

        public void Start(TaskScheduler scheduler)
        {
            if (scheduler == null)
            {
                Start(TaskFactory.StandardScheduler);
                return;
            }
            scheduler.EnqueueTask(this);
        }

        internal void DoWork()
        {
            if (ActionDelegate == null)
                return;

            status = TaskStatus.Running;
            try
            {
                WaitEvent.Reset();
                ExecuteDelegate();
            }
            catch (TargetInvocationException exception)
            {
                if (aggregateException == null)
                {
                    aggregateException = new AggregateException();
                }

                if (exception.InnerException is OperationCanceledException)
                {
                    aggregateException.InnerExceptions.Add(new TaskCanceledException());
                }

                if (status != TaskStatus.Canceled)
                {
                    aggregateException.InnerExceptions.Add(exception.InnerException);
                    status = TaskStatus.Faulted;
                }


                return;
            }
            finally
            {
                WaitEvent.Set();
            }

            status = TaskStatus.RanToCompletion;

            if (ContinueTask != null)
            {
                bool canContinue = true;
                switch (ContinueTaskContinuationOptions)
                {
                    case TaskContinuationOptions.None:
                        break;
                    case TaskContinuationOptions.OnlyOnCanceled:
                        canContinue = IsCanceled;
                        break;
                    case TaskContinuationOptions.OnlyOnFaulted:
                        canContinue = IsFaulted;
                        break;
                    case TaskContinuationOptions.OnlyOnRanToCompletion:
                        canContinue = (Status == TaskStatus.RanToCompletion);
                        break;
                    case TaskContinuationOptions.NotOnCanceled:
                        canContinue = !IsCanceled;
                        break;
                    case TaskContinuationOptions.NotOnFaulted:
                        canContinue = !IsFaulted;
                        break;
                    case TaskContinuationOptions.NotOnRanToCompletion:
                        canContinue = (Status != TaskStatus.RanToCompletion);
                        break;
                }

                if (canContinue)
                {
                    ContinueTask.Start(ContinueScheduler);
                }
            }
        }

        protected virtual void ExecuteDelegate()
        {
            if (ActionDelegate.Method.GetParameters().Length > 0)
                ActionDelegate.DynamicInvoke(AsyncState);
            else
                ActionDelegate.DynamicInvoke();
        }

        public void Wait()
        {
            WaitEvent.WaitOne();

            ThrowAggregateExceptionIfPresent();
        }

        protected void ThrowAggregateExceptionIfPresent()
        {
            if (aggregateException != null)
            {
                AggregateException result = aggregateException;
                aggregateException = null;
                throw result;
            }
        }

        public static void WaitAll(Task[] tasks)
        {
            AggregateException aggregateException = null;

            foreach (Task task in tasks)
            {
                try
                {
                    task.Wait();
                    task.Dispose();
                }
                catch (AggregateException source)
                {
                    if (aggregateException == null)
                    {
                        aggregateException = new AggregateException();
                    }

                    aggregateException.InnerExceptions.AddRange(source.InnerExceptions);
                }
            }

            if (aggregateException != null)
                throw aggregateException;
        }

        public void Dispose()
        {
            WaitEvent.WaitOne();
            WaitEvent.Dispose();
            waitEvent = null;
        }
    }
}
